/**
* @file SoundManager.cpp
* @author Max Ocklind
* @version 2010-01-11
*
* <code>SoundManager</code> is a (singleton) sound manager implementing OpenAL that plays sound
* effects based on priority. If there are no free sound source slots priority determines if and
* what slot should be replaced by a new sound by comparing the priorities of the sounds. Higher
* priority has precedence, and the new sound is ignored if it has the same priority as the sound
* with the lowest priority in the slots.
*
* <p><b>Note:</b> If you intend to pause, modify the properties of a sound source or play a long
* sound, you have to make sure that it has high enough priority. Otherwise the sound may be
* replaced with a higher priority request to play a sound.</p>
*
* <p><b>Units used in OpenAL:</b><br/>
* - Time: seconds (s)<br/>
* - Frequenzy: Hertz (Hz)<br/>
* - Space and distance: user defined</p>
**/


/* includes */

#include "SoundManager.h"


/* class SoundManager */

// initate singleton
SoundManager* sm = new SoundManager;


/* functions */

/* init functions */

/**
* Initiates OpenAL and the Effects Extension with the default audio device and the default context.
*
* @param metersPerUnit meters per user defined space and distance unit, used by the effects extension
*/
void SoundManager::init(ALfloat metersPerUnit)
{
	init(metersPerUnit, NULL);
}

/**
* Initiates OpenAL and the Effects Extension with the specified audio device and the default context.
*
* @param metersPerUnit meters per user defined space and distance unit, used by the effects extension
* @param deviceName the name of the audio device to use (NULL is used to select the default device)
*/
void SoundManager::init(ALfloat metersPerUnit, const ALCchar* deviceName)
{
	// get pointer to Open AL error checking class
	errCheck = ALErrorChecker::instancePtr();

	// initialize ALUT
	alutInitWithoutContext(NULL, NULL);
	errCheck->checkALUTError("SoundManager", "ALUT (init)");

	// open sound device
	ALCdevice* device = alcOpenDevice(deviceName);
	if (!device)
		if (AL_DEBUG)
			printf("ALC init error: failed to open ALC device \"%s\" (SoundManager)\n", deviceName);
		else
		{
			std::stringstream ss;
			ss << "failed to open ALC device \"" << deviceName << "\"" ;
			errCheck->throwError("ALC", "SoundManager", "init", ss.str().c_str());
		}

	// create context
	ALCcontext* context = alcCreateContext(device, NULL);
	if (!context)
		if (AL_DEBUG)
			printf("ALC init error: failed to create ALC context (SoundManager)\n");
		else
			errCheck->throwError("ALC", "SoundManager", "init", "failed to create ALC context");

	// set context
	alcMakeContextCurrent(context);
	errCheck->checkALCError("SoundManager", "init (set context)");

	// generate sources
	for (int i = 0; i < NUM_SOURCES; i++)
	{
		alGenSources(1, &mSources[i]);
		errCheck->checkALError("SoundManager", "init (generate sources)");

		// set sources of the Sound objects
		mSounds[i] = new Sound(mSources[i]);
	}

	// correct the sound type of the music sound slot
	delete mSounds[MUSIC];
	mSounds[MUSIC] = new StaticStreamingSound(mSources[MUSIC]);

	// init buffers, priorities, types, filters and associated timers
	mResetSoundManagerProperties();

	// disable distance culling by default
	disableDistanceCulling();


	/* EFX - Effects Extension */

	// check if Effects Extension is present
	if (!alcIsExtensionPresent(device, "ALC_EXT_EFX"))
		if (AL_DEBUG)
			printf("ALC init error: Effects Extension is not present (SoundManager)\n");
		else
			errCheck->throwError("ALC", "SoundManager", "init", "Effects Extension is not present");

	// set space and distance units
	alListenerf(AL_METERS_PER_UNIT, metersPerUnit);
	errCheck->checkALError("SoundManager", "init (meters per unit)");
}


/* Distance culling, doppler, volume and listener functions */

/**
* Disables distance culling of sounds to far away from the listener.
*/
void SoundManager::disableDistanceCulling()
{
	mDistanceCulling = false;
}

/**
* Enables distance culling of sounds to far away from the listener.
*
* @param cullMap a pointer to a map of different sound types and their respective culling distances
*/
void SoundManager::enableDistanceCulling(std::map<const char*, float>* cullMap)
{
	mCullingDistances = cullMap;
	mDistanceCulling  = true;
}

/**
* Checks if distance culling is enabled or disabled.
*
* @return returns <code>true</code> if distance culling is enabled, otherwise <code>false</code>.
*/
bool SoundManager::isDistanceCulling()
{
	return mDistanceCulling;
}

/**
* Enables the doppler effect and sets the doppler factor to 1.0 (default).
*/
void SoundManager::enableDopplerEffect()
{
	setDopplerFactor(1.0);
}

/**
* Disables the doppler effect (sets the doppler factor to 0.0).
*/
void SoundManager::disableDopplerEffect()
{
	setDopplerFactor(0.0);
}

/**
* Checks if the doppler effect is enabled.
*/
bool SoundManager::isDopplerEffect()
{
	return getDopplerFactor() != 0;
}

/**
* Sets the doppler factor used in doppler effect calculations.<br/>
* Setting the doppler factor to zero disables the doppler effect entierly.<br/>
* 1.0 is the default doppler effect value. The speed of sound will be set to 343.3 (default).
*
* @param factor the doppler factor, a non-negative float value
*/
void SoundManager::setDopplerFactor(ALfloat factor)
{
	setDopplerFactor(factor, 343.3f);
}

/**
* Sets the doppler factor and speed of sound used in doppler effect calculations.<br/>
* Setting the doppler factor to zero disables the doppler effect entierly.<br/>
* 1.0 is the default doppler factor value and 343.3 is the default speed of sound value.
*
* @param factor the doppler factor, a non-negative float value
* @param speedOfSound the speed of sound, a non-negative non-zero float value
*/
void SoundManager::setDopplerFactor(ALfloat factor, ALfloat speedOfSound)
{
	alDopplerFactor(factor);
	errCheck->checkALError("SoundManager", "setDopplerFactor (factor)");
	alSpeedOfSound(speedOfSound);
	errCheck->checkALError("SoundManager", "setDopplerFactor (speed of sound)");
}

/**
* Returns the doppler factor used in the doppler effect calculations.
*
* @return the doppler factor
*/
ALfloat SoundManager::getDopplerFactor()
{
	ALfloat factor;
	alGetFloatv(AL_DOPPLER_FACTOR, &factor);
	errCheck->checkALError("SoundManager", "getDopplerFactor");

	return factor;
}

/**
* Sets the master volume (the listener gain) to the specified value.
*
* @param gain the volume to be set (0.0 - 1.0)
*/
void SoundManager::setMasterVolume(ALfloat gain)
{
	alListenerf(AL_GAIN, gain);
	errCheck->checkALError("SoundManager", "setMasterVolume");
}

/**
* Returns the master volume (the gain of the listener).
*
* @return the master volume (gain) as a float
*/
ALfloat SoundManager::getMasterVolume()
{
	ALfloat gain;
	alGetListenerf(AL_GAIN, &gain);
	errCheck->checkALError("SoundManager", "getMasterVolume");
	
	return gain;
}

/**
* Sets the specified music volume (gain).
*
* @param gain the volume (gain) to be set (0.0 - 1.0)
*/
void SoundManager::setMusicVolume(ALfloat gain)
{
	mSounds[MUSIC]->setVolume(gain);
}

/**
* Returns the music volume (gain).
*
* @return the muisc volume (gain) as a float
*/
ALfloat SoundManager::getMusicVolume()
{
	return mSounds[MUSIC]->getVolume();
}

/**
* Sets the OpenAL listener position, velocity and orientation vectors, and the master gain.
*
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param orientation pointer to floating point-vector
*/
void SoundManager::setListenerProperties(ALfloat* position, ALfloat* velocity, ALfloat* orientation)
{
	alListenerfv(AL_POSITION, position);
	errCheck->checkALError("SoundManager", "setListenerProperties (position)");

	alListenerfv(AL_VELOCITY, velocity);
	errCheck->checkALError("SoundManager", "setListenerProperties (velocity)");
	
	alListenerfv(AL_ORIENTATION, orientation);
	errCheck->checkALError("SoundManager", "setListenerProperties (orientation)");
}

/**
* Calculates the distance between the specified 3D position (point) and the listener.
*
* @param sound apointer to the sound to check the distance to
* @param position pointer to floating point-vector (must be NULL if the sound is not already playing)
* @param when a string used for error reporting describing from where this function was called
* @return the distance between the listener position and the source position
*/
float SoundManager::distanceToListener(Sound* sound, ALfloat* position, const char* when)
{
	float x1, y1, z1,
		  dX, dY, dZ; // x2, y2, z2

	if (position == NULL)
	{
		// get sound source position
		alGetSource3f(sound->getSoundSource(), AL_POSITION, &x1, &y1, &z1);
		errCheck->checkALError("SoundManager", when);
	}
	else
	{
		x1 = position[0];
		y1 = position[1];
		z1 = position[2];
	}

	// get listener position
	alGetListener3f(AL_POSITION, &dX, &dY, &dZ);
	errCheck->checkALError("SoundManager", when);

	dX -= x1; // dX = x2 - x1
	dY -= y1; // dY = y2 - y1
	dZ -= z1; // dZ = z2 - z1

	return sqrt((dX * dX) + (dY * dY) + (dZ * dZ));
}


/* music functions */

/**
* Loads an Ogg Vorbis file for streaming with looping turned on by default.
*
* @param filePath the path to the Ogg Vorbis sound file that should be streamed
* @return an OpenAL handle to the music sound source
*/
Sound* SoundManager::loadMusic(const char* filePath)
{
	return loadMusic(filePath, AL_TRUE);
}

/**
* Loads an Ogg Vorbis file for streaming music with the specified looping state.
*
* @param filePath the path to the Ogg Vorbis sound file that should be streamed
* @param loop indicates if the music should loop or not
* @return an OpenAL handle to the music sound source
*/
Sound* SoundManager::loadMusic(const char* filePath, bool loop)
{
	ALfloat zeros[] = {0.0f, 0.0f, 0.0f};

	// load music
	((StaticStreamingSound*)mSounds[MUSIC])->play(filePath, MAX_PRIO, "", zeros, zeros, loop, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, 0.0f, NULL, NO_TIME_LIMIT);
	mSounds[MUSIC]->pause();

	return mSounds[MUSIC];
}

/**
* Plays the music without any sound effect filter enabled.<br/>
* This function will do nothing if the music is not loaded.
*/
void SoundManager::playMusic()
{
	playMusic(NULL, NO_TIME_LIMIT);
}

/**
* Plays the music with the specified sound effect filter enabled for the specified number of seconds.<br/>
* This function will do nothing if the music is not loaded.
* @param filter the sound effect filter that should be enabled on the source
* @param timeLimit the time in seconds that the sound effect filter should be enabled
*/
void SoundManager::playMusic(SFXFilter* filter, unsigned long timeLimit)
{
	mSounds[MUSIC]->enableSFXFilter(filter, timeLimit);
	mSounds[MUSIC]->play();
}

/**
* Pauses the music.<br/>
* This function will do nothing if the music is already paused or stopped.
*/
void SoundManager::pauseMusic()
{
	mSounds[MUSIC]->pause();
}

/**
* Resumes the music.<br/>
* This function will do nothing if the music is already playing or stopped.
*/
void SoundManager::resumeMusic()
{
	mSounds[MUSIC]->resume();
}

/**
* Stops the music.<br/>
* This function will do nothing if the music is already stopped.
*/
void SoundManager::stopMusic()
{
	mSounds[MUSIC]->stop();
}

/* sound functions */

/**
* Loads the specified sound file into a sound buffer.<br>
*
* @param filePath the path to the sound file that should be loaded
* @return a handle to the buffer containing the loaded sound data
*/
SoundBuffer SoundManager::loadSound(const char* filePath)
{
	SoundBuffer buffer = alutCreateBufferFromFile(filePath);
	std::stringstream ss;
	ss << "load sound (\"" << filePath << "\")";
	errCheck->checkALUTError("SoundManager", ss.str().c_str());

	// add buffer to buffer vector
	mSoundBuffers.push_back(buffer);

	return buffer;
}

/**
* Plays the specified sound if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playSound(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, bool isStatic)
{
	return playSound(buffer, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, isStatic, NULL, 0);
}

/**
* Plays the specified sound if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.<br/>
* The spefied sound effect filter is also enabled on the source for the specified number of seconds.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playSound(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, bool isStatic, SFXFilter* filter, unsigned long timeLimit)
{
	return playSound(buffer, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, isStatic, filter, timeLimit);
}

/**
* Plays the specified sound if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.<br/>
* The spefied sound effect filter is also enabled on the source for the specified number of seconds.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param loop indicates if the sound should loop or not
* @param pitch the pitch of the sound (0.0 - ...)
* @param gain the gain (contribution; 0.0 - 1.0) to the over all sound output
* @param rolloffFactor specifies how fast the sound should fade with distance (0.0 - ...)
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playSound(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, ALboolean loop, ALfloat pitch, ALfloat gain, ALfloat rolloffFactor, bool isStatic, SFXFilter* filter, unsigned long timeLimit)
{
	// check if sound is not static, and then distance culling if enabled
	if (!isStatic && mCheckCullSound(NULL, position, cullType, "playSound"))
		return SOUND_CULLED;

	SoundType soundType = NORMAL_SOUND;
	if (isStatic)
		soundType = STATIC_SOUND;

	// function broken up to improve performance in planned future thread safe support
	return mPlayIfPossible(buffer, NULL, priority, cullType, position, velocity, loop, pitch, gain, rolloffFactor, soundType, filter, timeLimit);
}

/**
* Streams the specified Ogg file if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.
*
* @param filePath the path to the Ogg Vorbis sound file that should be streamed
* @param priority the priority of the sound to be streamed
* @param cullType the culling type of the sound to be streamed
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playStreaming(const char* filePath, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, bool isStatic)
{
	return playStreaming(filePath, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, isStatic, NULL, 0);
}

/**
* Streams the specified Ogg file if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.<br/>
* The spefied sound effect filter is also enabled on the source for the specified number of seconds.
*
* @param filePath the path to the Ogg Vorbis sound file that should be streamed
* @param priority the priority of the sound to be streamed
* @param cullType the culling type of the sound to be streamed
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playStreaming(const char* filePath, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, bool isStatic, SFXFilter* filter, unsigned long timeLimit)
{
	return playStreaming(filePath, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, isStatic, filter, timeLimit);
}

/**
* Streams the specified sound if it is not culled by distance and there is a free sound slot or if
* the sound has higher priority than another playing sound. Static sounds are not culled by distance.<br/>
* The spefied sound effect filter is also enabled on the source for the specified number of seconds.
*
* @param filePath the path to the Ogg Vorbis sound file that should be streamed
* @param priority the priority of the sound to be streamed
* @param cullType the culling type of the sound to be streamed
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param loop indicates if the sound should loop or not
* @param pitch the pitch of the sound (0.0 - ...)
* @param gain the gain (contribution; 0.0 - 1.0) to the over all sound output
* @param rolloffFactor specifies how fast the sound should fade with distance (0.0 - ...)
* @param isStatic specifies if the sound should be played statically (without regard to sound 
* position or listener position)
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @return a handle to the sound if it could be played, otherwise <code>NULL</code> (if it did not 
* have high enough priority or if it is to far away from the source and distance culling is enabled)
*/
Sound* SoundManager::playStreaming(const char* filePath, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, ALboolean loop, ALfloat pitch, ALfloat gain, ALfloat rolloffFactor, bool isStatic, SFXFilter* filter, unsigned long timeLimit)
{
	// check if sound is not static, and then distance culling if enabled
	if (!isStatic && mCheckCullSound(NULL, position, cullType, "playStreaming"))
		return SOUND_CULLED;

	SoundType soundType = STREAMING_SOUND;
	if (isStatic)
		soundType = STATIC_STREAMING_SOUND;

	// function broken up to improve performance in planned future thread safe support
	return mPlayIfPossible(NULL, filePath, priority, cullType, position, velocity, loop, pitch, gain, rolloffFactor, soundType, filter, timeLimit);
}

/**
* Updates (buffers streaming sound data for) all the playing streaming sounds and checks the timers
* associated with the enabled sound effect filters.<br/>
* This function must be called from the main loop as often as possible (or often enough to not
* compromise stream playback and sound effect filtering time limits).
*/
void SoundManager::update()
{
	for (int i = 0; i < NUM_SOURCES; i++)
		mSounds[i]->update();
}

/**
* Pauses all playing sounds and the associated timers of enabled sound effect filters.
*/
void SoundManager::pauseSounds()
{
	// skip music (i = 0)
	for (int i = 1; i < NUM_SOURCES; i++)
		mSounds[i]->pause();
}

/**
* Resumes playing all the paused sounds and the associated timers of enabled sound effect filters.
*/
void SoundManager::resumeSounds()
{
	// skip music (i = 0)
	for (int i = 1; i < NUM_SOURCES; i++)
		mSounds[i]->resume();
}

/**
* Stops all sounds.
*/
void SoundManager::stopSounds()
{
	// skip music (i = 0)
	for (int i = 1; i < NUM_SOURCES; i++)
		mSounds[i]->stop();
}


/* sound and music functions */

/**
* Pauses the music and all sounds that are playing, as well as the associated timers of enabled
* sound effect filters.
*/
void SoundManager::pauseAll()
{
	pauseMusic();
	pauseSounds();
}

/**
* Resumes paused music and all paused sounds, as well as the associated timers of enabled sound
* effect filters. 
*/
void SoundManager::resumeAll()
{
	resumeMusic();
	resumeSounds();
}

/**
* Stops the music and all sounds.
*/
void SoundManager::stopAll()
{
	stopMusic();
	stopSounds();
}

/**
* Checks and tries to distance cull all sound sources by their respective distance culling type.
* Static sounds are not culled.<br/>
* This function could be useful for freeing up sound source slots if the listener moves around a
* lot and there are (longer) sounds playing to far away that you do not care about.
*
* @return <code>true</code> if one or more sounds were culled, otherwise <code>false</code>
*/
bool SoundManager::cullSounds()
{
	bool soundCulled = false;

	if (isDistanceCulling())
		// skip music (i = 0)
		for (int i = 1; i < NUM_SOURCES; i++)
			if (	!mIsStatic(mSounds[i]->getSoundType())
					&& mCheckCullSound(mSounds[i], NULL, mSounds[i]->getCullType(), "cullSounds")
				)
			{
				mSounds[i]->stop();
				soundCulled = true;
			}

	return soundCulled;
}

/**
* Checks and tries to distance cull all sound sources with lower priorities than the specified
* priority. Static sounds are not culled.<br/>
* This function could be useful for freeing up sound source slots if the listener moves around a
* lot and there are (longer) sounds playing to far away that you do not care about.
*
* @param priority all sounds with less priority than this will be tried for distance culling
* @return <code>true</code> if one or more sounds were culled, otherwise <code>false</code>
*/
bool SoundManager::cullSounds(int priority)
{
	return cullSounds(priority, -1);
}

/**
* Checks and tries to distance cull all sound sources outside the specified radius. Static sounds
* are not culled.<br/>
* This function could be useful for freeing up sound source slots if the listener moves around a
* lot and there are (longer) sounds playing to far away that you do not care about.
*
* @param radius only sound sources outside this radius are checked
* @return <code>true</code> if one or more sounds were culled, otherwise <code>false</code>
*/
bool SoundManager::cullSounds(float radius)
{
	return cullSounds(MAX_PRIO, radius);
}

/**
* Checks and tries to distance cull all sound sources outside the specified radius with lower
* priorities than the specified priority. Static sounds are not culled.<br/>
* This function could be useful for freeing up sound source slots if the listener moves around a
* lot and there are (longer) sounds playing to far away that you do not care about.
*
* @param priority all sounds with less priority than this will be tried for distance culling
* @param radius only sound sources outside this radius are checked
* @return <code>true</code> if one or more sounds were culled, otherwise <code>false</code>
*/
bool SoundManager::cullSounds(int priority, float radius)
{
	bool soundCulled = false;

	if (isDistanceCulling())
		// skip music (i = 0)
		for (int i = 1; i < NUM_SOURCES; i++)
			if (	!mIsStatic(mSounds[i]->getSoundType())
					&& mSounds[i]->getPriority() < priority
					&& distanceToListener(mSounds[i], NULL, "cullSounds") > radius
				)
			{
				mSounds[i]->stop();
				soundCulled = true;
			}

	return soundCulled;
}

/* Misc functions */

/**
* Stops all sounds and music, and deletes all generated buffers (loaded sounds).
*/
void SoundManager::deleteBuffers()
{
	// stop and detach sound buffers
	for (int i = 0; i < NUM_SOURCES; i++)
	{
		if (mIsStreaming(mSounds[i]->getSoundType()))
			mSounds[i]->reset(mSources[i]);
		else
		{
			mSounds[i]->stop();

			// detach sound buffer
			alSourcei(mSources[i], AL_BUFFER, NULL);
			errCheck->checkALError("SoundManager", "deleteBuffers (detach buffer)");
		}
	}

	// delete sound buffers
	for each (SoundBuffer buffer in mSoundBuffers)
	{
		alDeleteBuffers(1, &buffer);
		errCheck->checkALError("SoundManager", "deleteBuffers (delete buffer)");
	}

	// reset buffer vector, active priorities, active distance culling types and active sound types
	mResetSoundManagerProperties();
}

/**
* Enables the specified sound effect filter for the specified number of seconds on the music source
* and all sound sources.
*
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is a valid value and has the same effect as <code>disableMasterSFXFilter()</code>
* @param timeLimit the time in seconds that the sound effect filter should be enabled
*/
void SoundManager::enableMasterSFXFilter(SFXFilter* filter, unsigned long timeLimit)
{
	// disable currently enabled sound effect filter
	disableMasterSFXFilter();

	mMasterSFXFilter = filter;
	mMasterSFXFilterTimer.start(timeLimit);

	enableMusicSFXFilter( filter, timeLimit);
	enableSoundsSFXFilter(filter, timeLimit);
}

/**
* Disables any enabled sound effect filter on the music source and all sound sources.
*/
void SoundManager::disableMasterSFXFilter()
{
	mMasterSFXFilter = NULL;
	mMasterSFXFilterTimer.stop();
	mMasterSFXFilterTimer.reset();
		
	disableMusicSFXFilter();
	disableSoundsSFXFilter();
}

/**
* Enables the specified sound effect filter for the specified number of seconds on the music source.
*
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is a valid value and has the same effect as <code>disableMusicSFXFilter()</code>
* @param timeLimit the time in seconds that the sound effect filter should be enabled
*/
void SoundManager::enableMusicSFXFilter(SFXFilter* filter, unsigned long timeLimit)
{
	mSounds[MUSIC]->enableSFXFilter(filter, timeLimit);
}

/**
* Disables any enabled sound effect filter on the music source.
*/
void SoundManager::disableMusicSFXFilter()
{
	mSounds[MUSIC]->disableSFXFilter();
}

/**
* Enables the specified sound effect filter for the specified number of seconds on all sound sources.
*
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is a valid value and has the same effect as <code>disableSoundsSFXFilter()</code>
* @param timeLimit the time in seconds that the sound effect filter should be enabled
*/
void SoundManager::enableSoundsSFXFilter(SFXFilter* filter, unsigned long timeLimit)
{
	for (int i = 1; i < NUM_SOURCES; i++)
		mSounds[i]->enableSFXFilter(filter, timeLimit);
}

/**
* Disables any enabled sound effect filters on all sound sources.
*/
void SoundManager::disableSoundsSFXFilter()
{
	for (int i = 1; i < NUM_SOURCES; i++)
		mSounds[i]->disableSFXFilter();
}


/* private */

/**
* Plays the specified sound if there is a free sound slot, or if it has higher priority than a
* playing sound. In the last case it will replace the playing sound with the lowest priority.<br/>
* The specified sound effect filter is enabled for the sprecified numbers of seconds if there is no
* master sound effect filter enabled.
*
*<p><b>WARNING:</b> this function is not thread safe, and should thus have a synchronization
* mechanism, such as a mutex or a binary semaphore, added or only be called from within a block
* with such a synchronization mechanism, before being used in a thread-based sound playing
* environment. Using this with threads and without synchronization mechanism will cause the
* played sounds to replace each other by reverse calling order rather then by priority.</p>
*
* @param buffer the sound buffer of the sound to be played, this should be <code>NULL</code> if the
* sound should be streamed
* @param filePath the path to the Ogg Vorbis sound file that should be streamed, this should be
* <code>NULL</code> if the sound should not be streamed
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param loop indicates if the sound should loop or not
* @param pitch the pitch of the sound (0.0 - ...)
* @param gain the gain (contribution; 0.0 - 1.0) to the over all sound output
* @param rolloffFactor specifies how fast the sound should fade with distance (0.0 - ...)
* @param soundType specifies the type of the sound: <code>NORMAL_SOUND</code>, <code>STATIC_SOUND</code>,
* <code>STREAMING_SOUND</code> or <code>STATIC_STREAMING_SOUND</code>
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @return a handle to the sound if it could be played, otherwise <code>NULL</code>
*/
Sound* SoundManager::mPlayIfPossible(SoundBuffer buffer, const char* filePath, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, ALboolean loop, ALfloat pitch, ALfloat gain, ALfloat rolloffFactor, SoundType soundType, SFXFilter* filter, unsigned long timeLimit)
{
	// check if there is only room for music
	if (NUM_SOURCES <= 1)
		return NO_FREE_SOURCES;

	int  freeSourceIndex = 1; // skip music (i = 0)
	bool freeSource		 = false;

	// skip music (i = 0)
	for (int i = 1; i < NUM_SOURCES; i++)
	{
		// replace a stopped sound of the same type if possible
		if (mSounds[i]->getSoundType() == soundType && mSounds[i]->isStopped())
		{
			freeSourceIndex = i;
			freeSource		= true;
			break;
		}
		else if (mSounds[i]->getPriority() < mSounds[freeSourceIndex]->getPriority())
			freeSourceIndex = i; // update index of sound with lowest priority
	}

	// stop and replace lowest priority playing sound if there was no free source available
	if (!freeSource && !(priority > mSounds[1]->getPriority()) && !(priority > mSounds[freeSourceIndex]->getPriority()))
			return NO_FREE_SOURCES; // all playing sounds have higher priorities

	// check if the sound to be replaced is of another sound type
	if (!freeSource)
	{
		// delete current sound 
		delete mSounds[freeSourceIndex];

		// create a new sound of matching sound type
		switch (soundType)
		{
		case NORMAL_SOUND:
			mSounds[freeSourceIndex] = new Sound(mSources[freeSourceIndex]);
			break;
		case STATIC_SOUND:
			mSounds[freeSourceIndex] = new StaticSound(mSources[freeSourceIndex]);
			break;
		case STREAMING_SOUND:
			printf("STREAM");
			mSounds[freeSourceIndex] = new StreamingSound(mSources[freeSourceIndex]);
			printf("STREAM2");
			break;
		case STATIC_STREAMING_SOUND:
			printf("STAT_STREAM");
			mSounds[freeSourceIndex] = new StaticStreamingSound(mSources[freeSourceIndex]);
			printf("STAT_STREAM2");
			break;
		}
	}
	else
		mSounds[freeSourceIndex]->reset(mSources[freeSourceIndex]);

	// disable any active sound effect filter to make sure that the correct sound properties are
	// stored if enableing a sound effect filter
	mSounds[freeSourceIndex]->disableSFXFilter();

	// re-enable master sound effect filter to source if enabled
	if (mMasterSFXFilter)
		mSounds[freeSourceIndex]->enableSFXFilter(mMasterSFXFilter, mMasterSFXFilterTimer.getLimit());
	else if (filter)
		mSounds[freeSourceIndex]->enableSFXFilter(filter, timeLimit);

	// play sound TODO check polymorphism
	if (mIsStreaming(soundType))
		if (mIsStatic(soundType))
			((StaticStreamingSound*)mSounds[freeSourceIndex])->play(filePath, priority, cullType, position, velocity, loop, pitch, gain, rolloffFactor, filter, timeLimit);
		else
			((StreamingSound*)mSounds[freeSourceIndex])->play(filePath, priority, cullType, position, velocity, loop, pitch, gain, rolloffFactor, filter, timeLimit);
	else
		mSounds[freeSourceIndex]->play(buffer, priority, cullType, position, velocity, loop, pitch, gain, rolloffFactor, filter, timeLimit);

	return mSounds[freeSourceIndex];
}

/**
* Checks if a playing sound or a sound requesting to play can be distance culled (if
* distance culling is enabled).<br/>
* Beware that this function does not take the sound type into consideration, and will return
* <code>true</code> for static sounds as well if the listener is too far away from the dummy
* positions of the static sounds.
*
* @param sound a pointer to the sound to be checked for distance culling (setting this to
* <code>NULL</code> requires the position argument to be a valid non-NULL value)
* @param position pointer to floating point-vector (setting this to <code>NULL</code> forces the
* sound to get the position)
* @param cullType the culling type of the sound to be checked
* @param when a string used for error reporting describing from where this function was called
* @return <code>true</code> if the sound source is too far away and can be culled, otherwise
* <code>false</code>
*/
bool SoundManager::mCheckCullSound(Sound* sound, ALfloat* position, const char* cullType, const char* when)
{
	if(isDistanceCulling())
	{
		// sound can be culled if it is to far away from the listener
		std::map<const char*, float>::iterator iter = mCullingDistances->find(cullType);
		if (iter != mCullingDistances->end())
			if (distanceToListener(sound, position, when) > (iter->second))
				return true;
	}

	return false;
}

/*
* Checks if the specified sound type is static.
*
* @param soundType the sound type to be checked
* @return <code>true</code> if the sound type is <code>STREAMING_SOUND</code> or
* <code>STATIC_STREAMING_SOUND</code>, otherwise <code>false</code>
*/
bool SoundManager::mIsStatic(SoundType soundType)
{
	return (soundType == STREAMING_SOUND || soundType == STATIC_STREAMING_SOUND);
}

/*
* Checks if the specified sound type is streaming.
*
* @param soundType the sound type to be checked
* @return <code>true</code> if the sound type is <code>STREAMING_SOUND</code> or
* <code>STATIC_STREAMING_SOUND</code>, otherwise <code>false</code>
*/
bool SoundManager::mIsStreaming(SoundType soundType)
{
	return (soundType == STREAMING_SOUND || soundType == STATIC_STREAMING_SOUND);
}
/**
* Resets the sound buffer vector, the streaming sounds map, the priorities and types arrays, the
* master and sound effect filters and the associated timers.
*/
void SoundManager::mResetSoundManagerProperties()
{
	// empty buffer vector
	mSoundBuffers.clear();

	// reset priorities, types, filters and timers
	for (int i = 0; i < NUM_SOURCES; i++)
		mSounds[i]->reset(mSources[i]);

	// reset master sound effect filter and timer
	mMasterSFXFilter = NULL;
	mMasterSFXFilterTimer.stop();
	mMasterSFXFilterTimer.reset();
}